Skip to content

Docker buildx 构建 openjdk 多平台 jar 运行镜像

官方文档:https://docs.docker.com/build/building/multi-platform

前提

  • 需要 Docker 19.03及以上版本
  • 需要启用 docker buildx 插件
shell
# 查看 docker 版本
docker --version

# 查看 docker buildx 插件当前支持的架构(一般情况下只有 linux/amd64 (+3), linux/386)
docker buildx ls

假如我们要同时支持 linux/arm64 等其它平台,就需要做如下操作。

创建自定义构建器

这是由于 Docker 默认构建器(名称为:docker)不支持多平台构建(如同时构建 linux/amd64 和 linux/arm64)。需切换到支持多架构的构建器驱动。

由于国内网络问题,需要提前拉取以下镜像到本地(版本根据实际情况修改):

shell
docker pull moby/buildkit:buildx-stable-1

docker login --username=mengweijin@aliyun.com registry.cn-hangzhou.aliyuncs.com

docker pull registry.cn-hangzhou.aliyuncs.com/mengweijin/moby-buildkit:buildx-stable-1

docker tag registry.cn-hangzhou.aliyuncs.com/mengweijin/moby-buildkit:buildx-stable-1 moby/buildkit:buildx-stable-1

注意:如果用镜像加速器的话,需重新 tag 为 moby/buildkit:buildx-stable-1 原始名称。

shell
docker buildx create --name multi-builder --driver docker-container --bootstrap --use

# 查看当前构建器状态(带 * 号的为当前正在使用的构建器)
docker buildx ls     

# 如果创建失败可以删除
# 1. 先切换回默认构建器
docker buildx use default 

# 2. 删除构建器(根据名称)
docker buildx rm multi-builder
# 删除后,可重新创建

安装 QEMU

shell
# 使用 tonistiigi/binfmt 镜像安装 QEMU,并使用单个命令在主机上注册可执行类型:
# 这会在宿主机上注册各种架构的二进制格式处理程序,后续构建时 Docker 才能在不同的架构环境中执行命令
docker run --privileged --rm tonistiigi/binfmt --install all

docker run --privileged --rm registry.cn-hangzhou.aliyuncs.com/mengweijin/tonistiigi-binfmt:qemu-v10.0.4 --install all


# 每次启动后,都要再多安装几遍才能查到,不知道为啥
docker run --privileged --rm registry.cn-hangzhou.aliyuncs.com/mengweijin/tonistiigi-binfmt:qemu-v10.0.4 --install all

# 查看安装好的注册节点
ls -alh /proc/sys/fs/binfmt_misc

# 再次查看(发现已经有 linux/amd64 (+3), linux/arm64, linux/arm (+2), linux/ppc64le, (6 more) 等平台了)
docker buildx ls

注意

如果查看的时候没有,可以尝试等待一会儿,然后再次执行安装命令。多等一会儿,多试几次。 每次打包前都要使用 docker buildx ls 检查一下,因为一般 binfmt_misc 的配置在主机重启后会丢失。

关键参数:

  • --driver=docker-container:启用支持多平台构建的驱动。
    • 使用 docker-container 驱动在后台启动一个长期运行的构建容器。
    • 此容器集成 BuildKit 和 QEMU 模拟器,可跨架构编译镜像(如 x86 机器构建 ARM 镜像)
  • --bootstrap:立即启动构建器容器。
    • 未使用的后果:
      • 构建器处于 inactive 状态,需手动执行【docker buildx inspect multi-builder --bootstrap】激活。
  • --use:自动切换到此构建器,后续所有 docker buildx 命令默认使用它。
    • 验证方法:
      • docker buildx ls # 查看当前构建器,带 * 标识的为活跃实例

查看 QEMU 版本

shell
docker run --privileged --rm tonistiigi/binfmt --version

docker run --privileged --rm registry.cn-hangzhou.aliyuncs.com/mengweijin/tonistiigi-binfmt:latest --version

准备 Dockerfile

下面两个基础镜像只支持 linux/amd64,linux/arm64

  • openjdk:8-jdk-slim
  • openjdk:17-jdk-slim

确保源镜像存在且支持所需架构

并非所有官方镜像的所有版本都同时提供 amd64 和 arm64 架构。

建议先使用 docker manifest inspect 命令检查一下源镜像是否支持你需要的架构。

shell
docker pull openjdk:17-jdk-slim

docker manifest inspect openjdk:17-jdk-slim

其它镜像也可以通过 docker manifest inspect 命令来判断是否同时支持 amd64 和 arm64 架构。

注意:Dockerfile 中 FROM 也可以使用镜像加速器。比如:FROM docker.1ms.run/openjdk:17-jdk-slim

Dockerfile
# 注意:openjdk 官方镜像已停止维护,当前在 docker 仓库中已经找不到这个镜像了。
#ARG BASE_IMAGE=openjdk:17-jdk-slim

# 可以更换为 amazon 维护的 openjdk 镜像。
ARG BASE_IMAGE=amazoncorretto:17.0.17

FROM ${BASE_IMAGE}

LABEL maintainer="aday.fun@outlook.com"

# 编码
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8 
# 容器运行时环境
# ENV PATH=/usr/local/bin:$PATH

# 声明 JVM 参数
ENV JVM_OPTS="-Dserver.port=8080 -Dspring.profiles.active=dev -Duser.timezone=Asia/Shanghai -Xms512m -Xmx2048m"

ENV JAR="app.jar"

ENV DIR=/opt/app

# 设置时区(中国区)
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone 

# 创建挂载目录 
VOLUME ${DIR}

# 设置工作目录
WORKDIR ${DIR}

# 声明容器运行时监听的端口
EXPOSE 8080

# 设置容器启动时运行的命令
# 推荐使用 exec 格式(数组形式)而非 shell 格式,以避免参数解析问题
ENTRYPOINT ["sh", "-c", "java ${JVM_OPTS} -jar ${JAR}"]

关键点说明:

  • ARG: 构建参数
    • 功能:用于定义仅在 镜像构建阶段 有效的变量,可通过 --build-arg 动态赋值。
    • 语法:ARG 变量名[=默认值](默认值可省略,但未赋值时构建会失败)。
    • 作用域:仅在定义后的当前构建阶段有效,多阶段构建需分阶段重复声明。
    • 使用场景:动态指定基础镜像版本、应用名称、应用版本等。
  • ENV:环境变量
    • 功能:设置 构建阶段和容器运行时 均有效的环境变量,持久化到镜像中。
    • 语法:ENV 变量名=值 或 ENV 变量名1=值1 变量名2=值2...。
    • 作用域:全局生效,支持在后续指令(如 RUN、CMD)及容器内通过 printenv 查看。
    • 使用场景:配置容器运行时环境、与 ARG 结合,将构建参数转为持久化变量等。
    • 注意:
      • 变量引用:支持引用已定义的 ENV 变量(如 ENV A=1 B=$A)。
      • 覆盖规则:ENV 会覆盖同名的 ARG 变量。
      • 运行时修改:可通过 docker run --env 覆盖镜像中的 ENV 值。
  • 最佳实践
    • 敏感数据:优先使用 ENV 而非 ARG,避免通过构建历史泄露信息。
    • 多阶段构建:需在每个阶段重新声明 ARG。
    • 组合使用:可灵活使用 ENV 与 ARG 结合,将构建参数转为持久化变量。
  • ARG PLATFORM 和 --platform=${PLATFORM}:
    • 这些指令用于指定构建目标平台。PLATFORM 是一个由 buildx 自动传递的构建参数。
    • 它的值会根据 --platform 参数中指定的平台动态变化(例如 linux/amd64, linux/arm64)。
    • 在 FROM 指令中通过 --platform=${PLATFORM} 确保拉取与目标架构匹配的基础镜像。
  • 选择 openjdk:17-jdk-slim 是因为生产环境下体积较小,兼容行好,Debian 系操作相对熟悉。

多平台构建镜像

把 Dockerfile 文件放到安装了 docker 的主机上,比如:/home/Dockerfile

常规的 build 命令格式如下:

shell
# docker build 命令语法格式(最后的小数点表示构建当前目录下的 Dockerfile 文件)
docker build --build-arg <变量名1>=<1> --build-arg <变量名2>=<2> -t <镜像>:<> .

# 常规示例
docker build -t openjdk:17 .

但这显然不是我们要的多平台构建。

多平台构建如下

shell
cd /home

# Docker Hub
docker buildx build --platform linux/amd64,linux/arm64 -t mengweijin/openjdk:17 --push .

# aliyun Hub
docker login --username=mengweijin@aliyun.com registry.cn-hangzhou.aliyuncs.com

docker buildx build --platform linux/amd64,linux/arm64 -t registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17 --push .

命令参数解读:

  • --platform linux/amd64,linux/arm64
    • 明确指定要为之构建镜像的目标平台架构。
    • arm64 也常写作 aarch64,在 Docker 的上下文中,通常使用 linux/arm64 或 linux/arm64/v8
  • -t mengweijin/openjdk:17
    • 为生成的镜像命名。mengweijin 通常是你的 Docker Hub 用户名(如果使用其他仓库,请替换为完整的仓库地址)。
    • 其中的 openjdk:17 分别表示镜像名称(openjdk)和版本(17)。
  • --push
    • 表示将构建好的多架构镜像清单(manifest list)和各个架构的镜像推送到远程仓库。
    • 如果不使用 --push 参数,构建产生的镜像和清单仅存在于 buildx 的构建缓存中,而不会出现在本地的 Docker 镜像列表(docker images)中,也无法被直接使用或推送。
    • 若需在本地留存,可考虑分别构建不同架构的镜像并单独标签,但这样就失去了使用 buildx 一次构建多架构的便利性。
    • 所以建议推送。
shell
# 查看构建后的镜像信息是否同时包含 linux/amd64 和 linux/arm64
docker buildx imagetools inspect mengweijin/openjdk:17

docker buildx imagetools inspect registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17

通过 docker manifest inspect 命令检查拉取下来的镜像支持的架构信息

shell
docker pull registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17

docker manifest inspect registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17

仅查看当前镜像实际架构信息

shell
docker image inspect registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17 | grep Architecture

使用镜像

格式

shell
docker run \
--name <容器名> \
-p 8080:8080 \
--restart=on-failure:3 \
-e JVM_OPTS="-Dserver.port=8080 -Dspring.profiles.active=dev -Duser.timezone=Asia/Shanghai -Xms128m -Xmx512m" \
-e JAR="vita-admin-1.0.0-SNAPSHOT.jar" \
-v /opt/<挂载目>/:/opt/app/ \
-d openjdk:17

示例:

把 vita-admin-1.0.0-SNAPSHOT.jar 放到 /opt/vita/ 目录下,授文件夹权限 755。

shell
cd /opt

mkdir vita

chmod -R 755 /opt/vita

# 已推送的镜像
docker run --name vita -p 9006:8080 --restart=on-failure:3 \
--network network_default \
-e JVM_OPTS="-Dserver.port=8080 -Dspring.profiles.active=h2 -Duser.timezone=Asia/Shanghai -Xms64m -Xmx512m" \
-e JAR="vita-admin-1.0.0-SNAPSHOT.jar" \
-v /opt/vita/:/opt/app/ \
-d registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17

检查 docker run -e 参数在运行时是否被正确覆盖

shell
docker inspect --format='{{.Config.Env}}' <container_id>

docker inspect --format='{{.Config.Env}}' vita

验证时区

shell
# 进入容器
docker exec -it vita /bin/sh

# 检查系统时区(应显示 CST 时间)
date

Fri Feb 28 04:01:01 PM CST 2020

推送到阿里仓库

构建时,已经指定了 --push 参数,多平台构建下便不需要了。

若是常规 docker build 构建,则可参考。

shell
# 登录
docker login --username=mengweijin@aliyun.com registry.cn-hangzhou.aliyuncs.com

docker tag 1fb854a5d2a9 registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17

docker push registry.cn-hangzhou.aliyuncs.com/mengweijin/openjdk:17

docker rmi -f 1fb854a5d2a9